﻿using System;

using System.Collections.Generic;
using System.Text;
using PasswordSafe.Xml;
using Fluid.Controls;
using PasswordSafe.Properties;

namespace PasswordSafe.Classes
{
    /// <summary>
    /// Builds lists from data retreived from a PasswordReader to be used in ListBoxes.
    /// </summary>
    public class ListBuilder : IComparer<PasswordData>, IComparer<object>
    {
        private PasswordReader reader;

        private PasswordCategory defaultCategory;

        public PasswordCategory DefaultCategory
        {
            get
            {
                if (defaultCategory == null)
                {
                    CreateDefaultCategory();
                }
                return defaultCategory;
            }
        }

        /// <summary>
        /// Creates a new ListBuilder instance with data from a PasswordReader.
        /// </summary>
        /// <param name="reader">The PasswordReader class from which to retreive the data.</param>
        public ListBuilder(PasswordReader reader)
        {
            this.reader = reader;
        }

        private void CreateDefaultCategory()
        {
            PasswordCategory c = new PasswordCategory("Not Specified");
            c.IsDefault = true;
            c.Fields.Add(new FieldInfo("user", "Username"));
            c.Fields.Add(new FieldInfo("password", "Password"));
            c.Fields.Add(new FieldInfo("email", "EMail"));
            c.Fields.Add(new FieldInfo("url", "Url"));
            c.Fields.Add(new Separator());
            c.Fields.Add(new FieldInfo("info", "Info"));
            defaultCategory = c;
        }

        private NotifyList listBoxCategories;

        /// <summary>
        /// Gets an enumeration of all PasswordCategories.
        /// </summary>
        public IEnumerable<PasswordCategory> Categories
        {
            get
            {
                foreach (object item in listBoxCategories)
                {
                    PasswordCategory category = item as PasswordCategory;
                    if (category != null) yield return category;
                }
            }
        }

        /// <summary>
        /// Gets the list of categories including group headers for listboxes.
        /// </summary>
        public NotifyList ListBoxCategories
        {
            get { return GetCategories(); }
        }

        private NotifyList GetCategories()
        {
            if (listBoxCategories != null) return listBoxCategories;
            List<object> sortList = new List<object>();
            listBoxCategories = new NotifyList();
            foreach (PasswordCategory c in reader.Categories)
            {
                sortList.Add(c);
            }
            sortList.Add(DefaultCategory);
            sortList.Sort(this);
            foreach (object o in sortList) listBoxCategories.Add(o);
            all = new PasswordCategory("all");
            all.passwords = this.Passwords;
            all.ReadOnly = true;
            all.Title = "All Passwords";
            all.IsDefault = true;
            listBoxCategories.Insert(0, all);
            listBoxCategories.Insert(1, new GroupHeader());
            listBoxCategories.ModifyChanged += new EventHandler(ListModifyChanged);

            return listBoxCategories;
        }

        private PasswordCategory all;

        private List<PasswordData> passwords;

        public List<PasswordData> Passwords
        {
            get
            {
                EnsurePasswordList();
                return passwords;
            }
        }

        private bool modified;
        /// <summary>
        /// Gets if the data was modified and needs to be saved.
        /// </summary>
        public bool Modified
        {
            get { return modified; }
            set
            {
                if (modified != value)
                {
                    modified = value;
                    OnModifiedChanged();
                }
            }
        }

        private void OnModifiedChanged()
        {
            if (ModifiedChanged != null) ModifiedChanged(this, EventArgs.Empty);
        }


        public event EventHandler ModifiedChanged;

        void EnsurePasswordList()
        {
            if (passwords == null)
            {
                passwords = new List<PasswordData>();
                PasswordCategory defautCategory = DefaultCategory;
                foreach (PasswordData d in reader.Passwords)
                {
                    if (d.Category == null)
                    {
                        d.Category = defaultCategory;
                    }
                    passwords.Add(d);
                }
                passwords.Sort(this);
            }
        }

        private NotifyList currentPasswords;
        private List<object> passwordCopy = new List<object>();

        public NotifyList GetPasswords(PasswordCategory category, bool headers)
        {
            if (category == all) category = null;
            EnsurePasswordList();
            List<PasswordData> passwords = new List<PasswordData>();
            foreach (PasswordData d in this.passwords)
            {
                if (category == null || d.Category == category)
                {
                    passwords.Add(d);
                }
            }
            passwords.Sort(this);
            NotifyList sorted = new NotifyList();

            foreach (PasswordData d in passwords)
            {
                sorted.Add(d);
            }
            passwords.Clear();
            if (headers && sorted.Count > 3) sorted = AddHeaders(sorted);
            sorted.ModifyChanged += new EventHandler(ListModifyChanged);

            currentPasswords = sorted;
            passwordCopy.Clear();
            foreach (var p in sorted)
            {
                passwordCopy.Add(p);
            }

            return sorted;
        }


        void ListModifyChanged(object sender, EventArgs e)
        {
            Modified = true;
        }

        private NotifyList AddHeaders(NotifyList passwords)
        {
            NotifyList list = new NotifyList();

            string current = "";

            foreach (PasswordData d in passwords)
            {
                string c = d.Name.Substring(0, 1).ToUpper();

                if (current != c)
                {
                    current = c;
                    list.Add(new GroupHeader(c));
                }
                list.Add(d);
            }
            list.ModifyChanged += new EventHandler(ListModifyChanged);
            return list;

        }

        #region IComparer<PasswordData> Members

        int IComparer<PasswordData>.Compare(PasswordData x, PasswordData y)
        {
            return x.Name.CompareTo(y.Name);
        }

        #endregion

        #region IComparer<object> Members

        int IComparer<object>.Compare(object x, object y)
        {
            PasswordCategory a = x as PasswordCategory;
            if (a != null)
            {
                PasswordCategory b = y as PasswordCategory;
                return a.Name.CompareTo(b.Name);
            }
            return 0;
        }

        #endregion

        /// <summary>
        /// Gets or sets the instance for global use.
        /// </summary>
        /// <remarks>
        /// The main form sets the instance while other controls access the instance.
        /// </remarks>
        public static ListBuilder Instance;

        /// <summary>
        /// Creates a list of details of a password.
        /// </summary>
        /// <param name="password">the data of the password.</param>
        /// <param name="newItem">true if this password is new, otherwise false.</param>
        /// <returns></returns>
        public NotifyList GetDetails(PasswordData password, bool newItem)
        {
            NotifyList list = new NotifyList();

            PasswordCategory c = password.Category;

            if (c.Fields.Count == 0) c = DefaultCategory;

            foreach (FieldInfo field in c.Fields)
            {
                Separator separator = field as Separator;
                if (separator == null)
                {
                    string name = field.Name;
                    string value = password.Values[name];

                    PWNameValue item = new PWNameValue(field.Title + ":", field.Name, password);
                    list.Add(item);
                }
                else
                {
                    GroupHeader header = new GroupHeader(separator.Title);
                    list.Add(header);
                }
            }
            if (newItem)
            {
                PWNameValue nameValue = new PWNameValue("Name", "name", password);
                list.Insert(0, nameValue);
                GroupHeader gh = list[1] as GroupHeader;
                if (gh == null)
                {
                    list.Insert(1, new GroupHeader());
                }
            }
            list.Modified = false;
            list.ModifyChanged += new EventHandler(ListModifyChanged);


            return list;
        }

        public PasswordData CreateNewPassword(PasswordCategory category)
        {
            EnsurePasswordList();
            PasswordData data = new PasswordData();
            data.Category = category;
            passwords.Add(data);
            return data;
        }

        public void DeletePassword(PasswordData password)
        {
            Modified = true;
            password.Category = null;
            Passwords.Remove(password);
        }

        public void AddCategory(PasswordCategory category)
        {
            string text = category.Title;
            for (int i = 2; i < listBoxCategories.Count; i++)
            {
                PasswordCategory c = listBoxCategories[i] as PasswordCategory;
                if (c != null)
                {
                    if (c.Name == category.Name) return; //already in list, so return.
                    if (c.Title.CompareTo(text) > 0)
                    {
                        listBoxCategories.Insert(i, category);
                        return;
                    }
                }
            }
            listBoxCategories.Add(category);
        }

        public void RemoveCategory(PasswordCategory category)
        {
            listBoxCategories.Remove(category);
        }


        private List<CategoryListBoxItem> defaultCategories;

        /// <summary>
        /// Gets the list of all default categories.
        /// </summary>
        public List<CategoryListBoxItem> DefaultCategories
        {
            get
            {
                return GetDefaultCategories();
            }
        }

        private List<CategoryListBoxItem> GetDefaultCategories()
        {
            if (defaultCategories == null)
            {
                defaultCategories = new List<CategoryListBoxItem>();
                string xml = Resources.Categories;
                foreach (PasswordCategory c in PasswordReader.ReadCategories(xml))
                {
                    bool isUsed = false;
                    CategoryListBoxItem item = new CategoryListBoxItem { Category = c, IsUsed = isUsed };
                    defaultCategories.Add(item);
                }
            }
            foreach (var c in defaultCategories)
            {
                c.IsUsed = IsCategoryUsed(c.Category.Name);
            }
            return defaultCategories;
        }

        private bool IsCategoryUsed(string categoryName)
        {
            foreach (var c in Categories)
            {
                if (c.Name.Equals(categoryName, StringComparison.InvariantCultureIgnoreCase)) return true;
            }
            return false;
        }

        internal void AddCategories(List<CategoryListBoxItem> categories)
        {
            listBoxCategories.RaiseListChangedEvents = false;
            foreach (var item in categories)
            {
                if (item.IsUsed)
                {
                    AddCategory(item.Category);
                }
                else
                {
                    PasswordCategory category = GetCategory(item.Category.Name);
                    if (category != null) RemoveCategory(category);
                }
            }
            listBoxCategories.RaiseListChangedEvents = true;
            listBoxCategories.ResetBindings();
        }

        private PasswordCategory GetCategory(string name)
        {
            foreach (var c in this.Categories)
            {
                if (c.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) return c;
            }
            return null;
        }

        char[] matrix;

        private char[] EnsureMatrix()
        {
            if (matrix == null)
            {
                matrix = new char[256];
                for (int i = 0; i <= 255; i++)
                {
                    char c = Char.ToLower(Convert.ToChar(i));

                    if ("1".IndexOf(c) >= 0) c = '1';
                    else if ("2abc".IndexOf(c) >= 0) c = '2';
                    else if ("3def".IndexOf(c) >= 0) c = '3';
                    else if ("3ghi".IndexOf(c) >= 0) c = '4';
                    else if ("5jkl".IndexOf(c) >= 0) c = '5';
                    else if ("6mno".IndexOf(c) >= 0) c = '6';
                    else if ("7pqrs".IndexOf(c) >= 0) c = '7';
                    else if ("8tuv".IndexOf(c) >= 0) c = '8';
                    else if ("9wxyz".IndexOf(c) >= 0) c = '9';
                    else if ("# ".IndexOf(c) >= 0) c = '#';
                    else if ("0+".IndexOf(c) >= 0) c = '0';
                    else if (c == '*') c = '*';
                    else c = '\0';

                    matrix[i] = c;
                }
            }
            return matrix;
        }

        private string MakeSearchable(string text)
        {
            StringBuilder sb = new StringBuilder();

            char[] matrix = EnsureMatrix();
            foreach (char c in text)
            {
                int i = (int)c;
                if (i > 255) i = 255;
                char mc = matrix[i];
                if (mc != '\0')
                {
                    sb.Append(mc);
                }
            }
            return sb.ToString();
        }

        public void Filter(string text)
        {
            if (currentPasswords == null) return;

            bool modified = this.Modified;
            bool empty = string.IsNullOrEmpty(text);
            List<object> toAdd = new List<object>();
            foreach (object o in passwordCopy)
            {
                bool match = empty;
                if (!match)
                {
                    PasswordData p = o as PasswordData;
                    if (p != null)
                    {
                        string compare = MakeSearchable(p.Name);
                        match = compare.IndexOf(text) >= 0;
                    }
                }
                if (match)
                {
                    toAdd.Add(o);
                }
            }
            currentPasswords.RaiseListChangedEvents = false;
            currentPasswords.Clear();
            foreach (object o in toAdd)
            {
                currentPasswords.Add(o);
            }
            currentPasswords.RaiseListChangedEvents = true;
            currentPasswords.ResetBindings();

            // do not accidantely mark the data as begin modified:
            this.Modified = modified;
        }

        public void UnModified()
        {
            Modified = false;
        }
    }
}
